Compute the hover overlay position from live rects (fix first-frame jump)#5126
Merged
Conversation
Contributor
5b6423d to
63412bb
Compare
…rame jump The hover overlay is positioned by velcro (floating-ui), but it only became `position: absolute` inside the offset middleware — which runs *after* floating-ui has already measured the rects on the first computePosition. So the first frame was measured against the wrong containing block and the whole overlay (the teal type-label tab, the select chip, the menu, the outline) painted ~60px off, then snapped into place one frame later. floating-ui requires the floating element to already be absolutely positioned when it first measures. Declare `position: absolute` in the overlay's CSS so it is in effect before velcro's first measurement; the middleware still sets it too, harmlessly. Confirmed via frame-by-frame capture that the overlay's own top/left was the thing jumping (526->466 between frame 0 and 1), so fixing it here fixes every piece of chrome that rides the overlay. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
63412bb to
561c680
Compare
Gated debug logging (localStorage['adorn-pos-debug']='1' or ?adornPosDebug=1) in the offset middleware: per computePosition, logs the returned x/y, the reference card's rect, the floating element's computed position + offset parent + that parent's top, and scroll offsets — so we can see which quantity is ~60px off on the first call. Includes a v-mw1 build marker. Diagnostic only; keeps the position:absolute CSS so we can also confirm whether it took effect (floatPos field). Do not merge. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frame-by-frame capture of the velcro offset middleware showed floating-ui's first one-or-two computePosition calls return the reference card in viewport coordinates instead of offset-parent-relative ones — it omits the offset parent's offset (here 60px) for ~1 frame, then corrects. So the overlay, and everything riding it (the teal type-label tab, the select chip, the menu, the outline), painted ~60px off and visibly jumped into place on first hover. Keep the overlay hidden until its position settles: in the offset middleware, set opacity 0 and reschedule a reveal one animation frame after the most recent position write, so it reveals only once movement stops (the wrong value can repeat across calls, so converging on equality isn't enough). Opacity — not visibility — so the overlay's action buttons stay hit-testable throughout. Gated behind `hideUntilPositioned`, which only OperatorModeOverlays (the overlay with visible chrome) turns on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous attempt rescheduled the reveal off requestAnimationFrame inside the offset middleware, but floating-ui's correction to the right position also lands a frame later, so the reveal could win that race and flash the overlay at the wrong (first-measure) position before it corrected. Watch the overlay's actual measured rect instead: hold it at opacity 0 from before the first paint (in a modifier) and reveal only once getBoundingClientRect is unchanged for two consecutive frames (with a safety cap). The wrong position only appears for a single frame, so it can never be the reveal frame, and there is no dependence on middleware-output equality or frame counts. Opacity, not visibility, so the overlay's action buttons stay hit-testable. Gated behind hideUntilPositioned, which only OperatorModeOverlays enables. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…jump The velcro offset middleware returned floating-ui's `rects.reference`, but frame-by-frame capture showed floating-ui's first one-or-two computePosition calls return the reference in viewport coordinates and only subtract the offset parent's offset a frame later — so the overlay (and everything riding it: the type-label tab, the select chip, the menu, the outline) painted ~60px off and jumped into place on first hover. The capture also showed the inputs we need are correct from frame 0: the reference's own getBoundingClientRect and the floating element's offsetParent rect. Compute the position from those directly (recovering the offset parent's scale the same way the Adorn label positioner does, for the scaled test runner), so it's right on the very first frame. No hiding, no rAF, no deferred reveal — the overlay simply lands in the correct place immediately. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jurgenwerk
approved these changes
Jun 5, 2026
burieberry
approved these changes
Jun 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the first-frame jump on the CardsGrid hover chrome — the teal Adorn type-label tab, the select chip, the menu, and the outline all painting ~60px off for one frame then snapping into place.
Root cause (confirmed by instrumenting the velcro offset middleware)
The hover overlay is positioned by velcro (floating-ui). The middleware returned floating-ui's
rects.reference, but frame-by-frame capture showed floating-ui's first one-or-twocomputePositioncalls return the reference in viewport coordinates (y=200.4) and only subtract the offset parent's offset (opTop=60) a frame later (y=140.4). Everything else was constant the whole time. So the overlay painted atviewport - 0for a frame, then corrected toviewport - offsetParent, and everything riding it jumped with it.Fix
The capture also showed the inputs needed are correct from frame 0: the reference's own
getBoundingClientRectand the floating element'soffsetParentrect. Compute the position from those directly —(refRect.left − parentRect.left) / scaleX,(refRect.top − parentRect.top) / scaleY, recovering the offset parent's scale the same way the Adorn label positioner does for the scaled test runner — instead of trusting floating-ui'srects.reference. It's correct on the very first frame. No hiding, no rAF, no deferred reveal; the overlay simply lands in the right place immediately.History
Earlier revisions chased label height, then
position: absolutetiming, then hiding the overlay until settle (opacity gate / rect-stability watch). The middleware capture disproved each: height was constant,floatPoswas alreadyabsolute, and hiding fought a rAF-vs-correction race. This addresses the actual returned coordinate.Notes
Overlaysbase; benefits every velcro overlay consumer.🤖 Generated with Claude Code